﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD {
    /// <summary>
    /// 3要素のベクトル構造体
    /// </summary>
    public struct Vector3<T> : IExportable<T>, IAnimatable<T>, IEquatable<Vector3<T>> where T : struct {

        #region Constructor
        /// <summary>
        /// XYZ要素を個別に指定するコンストラクター
        /// </summary>
        /// <param name="x">X要素</param>
        /// <param name="y">Y要素</param>
        /// <param name="z">Z要素</param>
        public Vector3 (T x, T y, T z)
            : this () {
            this.X = x;
            this.Y = y;
            this.Z = z;
            this.IsNormalized = false;
        }

        /// <summary>
        /// 要素を配列で指定するコンストラクター
        /// </summary>
        /// <param name="xyz">XYZ要素</param>
        public Vector3 (T[] xyz)
            : this () {
            if (xyz == null) {
                throw new ArgumentNullException ("xyz is null");
            }
            if (xyz.Length < 3) {
                throw new ArgumentException ("xyz is too short");
            }

            this.X = xyz[0];
            this.Y = xyz[1];
            this.Z = xyz[2];
            this.IsNormalized = false;
        }

        /// <summary>
        /// 要素を2要素のベクトルとTで指定するコンストラクター
        /// </summary>
        /// <param name="v">2要素のベクトル</param>
        /// <param name="z">Z要素</param>
        public Vector3 (Vector2<T> v, T z)
            : this () {
            this.X = v.X;
            this.Y = v.Y;
            this.Z = z;
            this.IsNormalized = false;
        }


        #endregion

        #region Property
        /// <summary>
        /// X要素
        /// </summary>
        public T X {
            get;
            private set;
        }

        /// <summary>
        /// Y要素
        /// </summary>
        public T Y {
            get;
            private set;
        }

        /// <summary>
        /// Z要素
        /// </summary>
        public T Z {
            get;
            private set;
        }

        /// <summary>
        /// 正規化済みかどうかのフラグ
        /// </summary>
        public bool IsNormalized {
            get;
            private set;
        }



        /// <summary>
        /// コンポーネント数
        /// </summary>
        public int ComponentCount {
            get { return 3; }
        }

        /// <summary>
        /// コンポーネントにアクセスするインデクサー
        /// </summary>
        /// <param name="index">インデックス</param>
        /// <returns>構成要素</returns>
        public T this[int index] {
            get {
                switch (index) {
                    case 0: return X;
                    case 1: return Y;
                    case 2: return Z;
                    default: return default(T);
                }
            }
            set {
                switch (index) {
                    case 0: X = value; break;
                    case 1: Y = value; break;
                    case 2: Z = value; break;
                    default: break;
                }
            }
        }

        /// <summary>
        /// ベクトルの長さ
        /// </summary>
        public T Length {
            get { return (T)Math.Sqrt ((dynamic)X * X + (dynamic)Y * Y + (dynamic)Z * Z); }
        }

        /// <summary>
        /// ベクトルの長さの2乗
        /// </summary>
        public T Length2 {
            get { return (dynamic)X * X + (dynamic)Y * Y + (dynamic)Z * Z; }
        }

        #endregion


        #region Method
        /// <summary>
        /// このベクトルを正規化したベクトルを作成します
        /// </summary>
        /// <returns>正規化済みのベクトル</returns>
        public Vector3<T> Normalize () {
            if (IsNormalized) {
                return this;
            }
            var v = new Vector3<T> ((dynamic)X / Length, (dynamic)Y / Length, (dynamic)Z / Length);
            v.IsNormalized = true;
            return v;
        }

        /// <summary>
        /// ベクトルとfloatのかけ算
        /// </summary>
        /// <param name="v">ベクトル</param>
        /// <param name="f">floatの値</param>
        /// <returns></returns>
        public static Vector3<T> operator * (Vector3<T> v, float f) {
            return new Vector3<T> ((dynamic)v.X * f, (dynamic)v.Y * f, (dynamic)v.Z * f);
        }

        /// <summary>
        /// floatとベクトルのかけ算
        /// </summary>
        /// <param name="f">floatの値</param>
        /// <param name="v">ベクトル</param>
        /// <returns></returns>
        public static Vector3<T> operator * (float f, Vector3<T> v) {
            return v * f;
        }

        /// <summary>
        /// ベクトルとfloatの割り算
        /// </summary>
        /// <param name="v">ベクトル</param>
        /// <param name="f">floatの値</param>
        /// <returns></returns>
        public static Vector3<T> operator / (Vector3<T> v, float f) {
            if (f == 0) {
                throw new ArgumentException ("Divided by 0");
            }
            return new Vector3<T> ((dynamic)v.X / f, (dynamic)v.Y / f, (dynamic)v.Z / f);
        }

        /// <summary>
        /// ベクトル同士の足し算
        /// </summary>
        /// <remarks>
        /// 2つのベクトルをコンポーネント要素毎に足して新しいベクトルを作成します
        /// </remarks>
        /// <param name="v1">ベクトル１</param>
        /// <param name="v2">ベクトル２</param>
        /// <returns></returns>
        public static Vector3<T> operator + (Vector3<T> v1, Vector3<T> v2) {
            return new Vector3<T> ((dynamic)v1.X + v2.X, (dynamic)v1.Y + v2.Y, (dynamic)v1.Z + v2.Z);
        }

        /// <summary>
        /// ベクトル同士の引き算
        /// </summary>
        /// <remarks>
        /// 2つのベクトルをコンポーネント要素毎に引いて新しいベクトルを作成します
        /// </remarks>
        /// <param name="v1">ベクトル１</param>
        /// <param name="v2">ベクトル２</param>
        /// <returns></returns>
        public static Vector3<T> operator - (Vector3<T> v1, Vector3<T> v2) {
            return new Vector3<T> ((dynamic)v1.X - v2.X, (dynamic)v1.Y - v2.Y, (dynamic)v1.Z - v2.Z);
        }

        /// <summary>
        /// ベクトルを反転する
        /// </summary>
        /// <param name="v">ベクトル</param>
        /// <returns>反転したベクトル</returns>
        public static Vector3<T> operator - (Vector3<T> v) {
            return new Vector3<T> (-(dynamic)v.X, -(dynamic)v.Y, -(dynamic)v.Z);
        }

        /// <summary>
        /// ベクトルの内積
        /// </summary>
        /// <param name="a">ベクトルA</param>
        /// <param name="b">ベクトルB</param>
        /// <returns>内積値</returns>
        public static T Dot (Vector3<T> a, Vector3<T> b) {
            return (dynamic)a.X * b.X + (dynamic)a.Y * b.Y + (dynamic)a.Z * b.Z;
        }

        /// <summary>
        /// ベクトルの外積
        /// </summary>
        /// <param name="a">ベクトルA</param>
        /// <param name="b">ベクトルB</param>
        /// <returns>外積値</returns>
        public static Vector3<T> Cross (Vector3<T> a, Vector3<T> b) {
            return new Vector3<T> ((dynamic)a.Y * b.Z - (dynamic)b.Y * a.Z, (dynamic)a.Z * b.X - (dynamic)b.Z * a.X, (dynamic)a.X * b.Y - (dynamic)b.X * a.Y);
        }

        /// <summary>
        /// 上方向の単位ベクトル
        /// </summary>
        /// <returns></returns>
        public static Vector3<T> Up {
            get { return new Vector3<T> ((dynamic)0, (dynamic)1, (dynamic)0); }
        }

        /// <summary>
        /// 下方向の単位ベクトル
        /// </summary>
        /// <returns></returns>
        public static Vector3<T> Down {
            get { return new Vector3<T> ((dynamic)0, (dynamic)(-1), (dynamic)0); }
        }

        /// <summary>
        /// 右方向の単位ベクトル
        /// </summary>
        /// <returns></returns>
        public static Vector3<T> Right {
            get { return new Vector3<T> ((dynamic)1, (dynamic)0, (dynamic)0); }
        }

        /// <summary>
        /// 左方向の単位ベクトル
        /// </summary>
        /// <returns></returns>
        public static Vector3<T> Left {
            get { return new Vector3<T> ((dynamic)(-1), (dynamic)0, (dynamic)0); }
        }


        /// <summary>
        /// 前方向の単位ベクトル
        /// </summary>
        /// <remarks>
        /// DDDでは「前方向」を-z軸(0,0,-1)と定義しています。
        /// </remarks>
        /// <returns></returns>
        public static Vector3<T> Forward {
            get { return new Vector3<T> ((dynamic)0, (dynamic)0, (dynamic) (-1)); }
        }

        /// <summary>
        /// 後ろ方向の単位ベクトル
        /// </summary>
        /// <remarks>
        /// DDDでは「後ろ方向」をz軸(0,0,1)と定義しています。
        /// </remarks>
        /// <returns></returns>
        public static Vector3<T> Back {
            get { return new Vector3<T> ((dynamic)0, (dynamic)0, (dynamic)1); }
        }

        /// <summary>
        /// 原点(0,0,0)
        /// </summary>
        /// <returns></returns>
        public static Vector3<T> Zero {
            get { return new Vector3<T> ((dynamic)0, (dynamic)0, (dynamic)0); }
        }


        /// <summary>
        /// 全要素を<typeparamref name="T"/>の配列に変換して取り出します
        /// </summary>
        /// <param name="v">変換元のベクトル</param>
        /// <returns>変換した配列</returns>
        public static explicit operator T[] (Vector3<T> v) {
            return new T[] { v[0], v[1], v[2] };
        }

        /// <summary>
        /// 同値性を比較します
        /// </summary>
        /// <remarks>
        /// 同値性を比較する汎用の比較関数です。
        /// <see cref="Vector3{T}"/>オブジェクトはコンポーネント要素が厳密に一致しなくても、
        /// 全てのコンポーネント要素で差が<see cref="GlobalSettings.Tolerance"/>以下であれば等しいと見なされます。
        /// </remarks>
        /// <param name="obj">右辺のオブジェクト</param>
        /// <returns>等しいときtrue, そうでないときfalse</returns>
        public override bool Equals (object obj) {
            if (object.ReferenceEquals (obj, null)) {
                return false;
            }
            if (object.ReferenceEquals (obj, this)) {
                return true;
            }
            var v = obj as Vector3<T>?;
            if (v == null) {
                return false;
            }

            return this.Equals (v.Value);
        }

        /// <summary>
        /// タイプセーフな同値性を比較します
        /// </summary>
        /// <remarks>
        /// <see cref="Vector3{T}"/>型の同値性を比較します。
        /// </remarks>
        /// <param name="v">右辺の<see cref="Vector3{T}"/>オブジェクト</param>
        /// <returns>等しいときtrue, そうでないときfalse</returns>
        public bool Equals (Vector3<T> v) {
            return (Math.Abs ((dynamic)X - v.X) < GlobalSettings.Tolerance) &&
                    (Math.Abs ((dynamic)Y - v.Y) < GlobalSettings.Tolerance) &&
                    (Math.Abs ((dynamic)Z - v.Z) < GlobalSettings.Tolerance);
        }

        /// <summary>
        /// 等号による同値性を比較します
        /// </summary>
        /// <param name="left">左項</param>
        /// <param name="right">右項</param>
        /// <returns>等しいときtrue, そうでないときfalse</returns>
        public static bool operator == (Vector3<T> left, Vector3<T> right) {
            return left.Equals (right);
        }

        /// <summary>
        /// 等号による同値性を比較します
        /// </summary>
        /// <param name="left">左項</param>
        /// <param name="right">右項</param>
        /// <returns>等しくないときtrue, そうでないときfalse</returns>
        public static bool operator != (Vector3<T> left, Vector3<T> right) {
            return !(left == right);
        }

        /// <summary>
        /// 一意のハッシュ値を取得します
        /// </summary>
        /// <remarks>
        /// このメソッドは同値性の比較メソッドと異なり
        /// <see cref="GlobalSettings.Tolerance"/>の影響を受けません。
        /// </remarks>
        /// <returns></returns>
        public override int GetHashCode () {
            return base.GetHashCode ();
        }


        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>現在のオブジェクトを説明する文字列</returns>
        public override string ToString () {
            return string.Format ("Vector3: X={0}, Y={1}, Z={2}", X, Y, Z);
        }

        #endregion

    }
}
